package cn.itrigger.dynamic.sql.select.render;

import cn.itrigger.dynamic.sql.render.RenderingStrategy;
import cn.itrigger.dynamic.sql.select.GroupByModel;
import cn.itrigger.dynamic.sql.select.QueryExpressionModel;
import cn.itrigger.dynamic.sql.select.join.JoinModel;
import cn.itrigger.dynamic.sql.structure.BasicColumn;
import cn.itrigger.dynamic.sql.structure.SqlTable;
import cn.itrigger.dynamic.sql.util.CustomCollectors;
import cn.itrigger.dynamic.sql.where.WhereModel;
import cn.itrigger.dynamic.sql.where.render.WhereClauseProvider;
import cn.itrigger.dynamic.sql.where.render.WhereRenderer;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author admin
 * @date 2018/9/26
 **/
public class QueryExpressionRenderer {
    private QueryExpressionModel queryExpression;
    private RenderingStrategy renderingStrategy;
    private AtomicInteger sequence;

    private QueryExpressionRenderer(Builder builder) {
        this.queryExpression = Objects.requireNonNull(builder.queryExpression);
        this.renderingStrategy = Objects.requireNonNull(builder.renderingStrategy);
        this.sequence = Objects.requireNonNull(builder.sequence);
    }

    public QueryExpression render() {
        return QueryExpression.withColumnList(calculateColumnList())
                .withConnector(queryExpression.getConnector())
                .isDistinct(queryExpression.isDistinct())
                .withTableName(calculateTableName(queryExpression.getSqlTable()))
                .withJoinClause(queryExpression.getJoinModel().map(this::renderJoin))
                .withWhereClause(queryExpression.getWhereModel().flatMap(this::renderWhereClause))
                .withGroupByClause(queryExpression.getGroupByModel().map(this::renderGroupBy))
                .build();
    }

    private String calculateColumnList() {
        return queryExpression.mapColumns(this::applyTableAndColumnAlias)
                .collect(Collectors.joining(", "));
    }

    private String calculateTableName(SqlTable sqlTable) {
        return queryExpression.calculateTableNameIncludingAlias(sqlTable);
    }

    private String applyTableAndColumnAlias(BasicColumn selectListItem) {
        return selectListItem.renderWithTableAndColumnAlias(queryExpression.getTableAliasCalculator());
    }

    private String renderJoin(JoinModel joinModel) {
        return JoinRenderer.withJoinModel(joinModel)
                .withQueryExpression(queryExpression)
                .build()
                .render();
    }

    private Optional<WhereClauseProvider> renderWhereClause(WhereModel whereModel) {
        return WhereRenderer.withWhereModel(whereModel)
                .withRenderingStrategy(renderingStrategy)
                .withTableAliasCalculator(queryExpression.getTableAliasCalculator())
                .withSequence(sequence)
                .build()
                .render();
    }

    private String renderGroupBy(GroupByModel groupByModel) {
        return groupByModel.mapColumns(this::applyTableAlias)
                .collect(CustomCollectors.joining(", ", "group by ",""));
    }

    private String applyTableAlias(BasicColumn column) {
        return column.renderWithTableAlias(queryExpression.getTableAliasCalculator());
    }

    public static Builder withQueryExpression(QueryExpressionModel queryExpression) {
        return new Builder().withQueryExpression(queryExpression);
    }

    public static class Builder {
        private QueryExpressionModel queryExpression;
        private RenderingStrategy renderingStrategy;
        private AtomicInteger sequence;

        public Builder withQueryExpression(QueryExpressionModel queryExpression) {
            this.queryExpression = queryExpression;
            return this;
        }

        public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) {
            this.renderingStrategy = renderingStrategy;
            return this;
        }

        public Builder withSequence(AtomicInteger sequence) {
            this.sequence = sequence;
            return this;
        }

        public QueryExpressionRenderer build() {
            return new QueryExpressionRenderer(this);
        }
    }
}
